/*-
 * PunctuationProcessor.java
 *
 * $LastChangedBy: stein $
 * $LastChangedRevision: 95 $
 * $LastChangedDate: 2007-02-13 16:19:24 +0000 (Tue, 13 Feb 2007) $
 * $HeadURL: svn://localhost/SMS/src/com/googlecode/attention/sms/comp/PunctuationProcessor.java $
 *
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.sms.comp;

import java.io.IOException;

import com.googlecode.attention.sms.pdu.CBS;
import com.googlecode.attention.util.IntArrayBuilder;
import com.googlecode.attention.util.Settings;


/** * Punctuation Processor used to Compress/Decompress user data. * <BR>See 3GPP TS 23.042 Version 5.0.0 Release 5 *  * @author Stein Gunnar Bakkeby */
public class PunctuationProcessor {
  /* Field values */
  private int   PU_IWS; // Inter-Word Separator
  private int   PU_LST; // Last Sentence Terminator
  private int[] PU_WSF; // Word Separator Follows
  private int[] PU_UCF; // Upper Case Follows
  private int[] PU_UCW; // Upper Case Word
  private int[] PU_NSI; // No Separator Insertion
  private CH previousCH;
  private int pp_id = 0;
  
  /**
   * Constructor, creates the punctuation processor using
   * a compression header.
   * 
   * @param ch The compression header
   * @see #changeInitialisation(CH)
   * @throws IOException If initialisation file cannot be read
   */
  public PunctuationProcessor(final CH ch) throws IOException {
    changeInitialisation(ch);
  }
  
  /**
   * This will change the initialisation parameters based
   * upon a compression header.
   * 
   * @param ch The compression header
   * @throws IOException If initialisation file cannot be read
   */
  public void changeInitialisation(final CH ch) throws IOException {
    if (previousCH == null
        || previousCH.isUpdateNeeded(ch, CH.CHANGE_PUNCTUATOR)) {
      final String lang = CBS.parse(ch.getCLC()).toString().toLowerCase();
      /* If change initialisation ID */
      if (ch.getExtensionValue(CH.CHANGE_PUNCTUATOR) != -1)
        pp_id = ch.getExtensionValue(CH.CHANGE_PUNCTUATOR);
      String path = this.getClass().getCanonicalName();
      path = path.substring(0, path.lastIndexOf("."));
      String resource = path + "."
                      + lang
                      + "_punctuation_"
                      + pp_id;
      
      if (pp_id == 0) {
        PU_IWS = 0;
        PU_LST = 0;
        PU_WSF = new int[] {0};
        PU_UCF = new int[] {0};
        PU_UCW = new int[] {0};
        PU_NSI = new int[] {0};
      } else {    
        Settings s = new Settings(resource);
        
        /* Inter-Word Separator */
        PU_IWS = s.getInteger("pu_iws");
        
        /* Last Sentence Terminator */
        PU_LST = s.getInteger("pu_lst");

        /* Word Separator Follows */
        PU_WSF = s.getIntegers("pu_wsf_");
        
        /* Upper Case Follows */
        PU_UCF = s.getIntegers("pu_ucf_");
        
        /* Upper Case Word */
        PU_UCW = s.getIntegers("pu_ucw_");
        
        /* No Separator Insertion */
        PU_NSI = s.getIntegers("pu_nsi_");
      }
    }
    previousCH = new CH(ch);
  }
  
  /**
   * This will use the punctuation processor to compress data.
   * 
   * @param data The user data
   * @return The compressed data
   */
  public int[] compress(final int[] data) {
    int length = data.length;
    IntArrayBuilder output = new IntArrayBuilder(length);
    boolean someHasNotHadIWS = false;
    int p = -1; // previous character
    int c = -1; // current character
    boolean p_iws = false; boolean c_iws = false;
    boolean p_wsf = false; boolean c_wsf = false;
    boolean p_ucf = false; boolean c_ucf = false;
    boolean p_ucw = false; boolean c_ucw = false;
    boolean p_lst = false; boolean c_lst = false;
    boolean p_nsi = false; boolean c_nsi = false;
    
    /* Step 1 */
    for (int i = 0; i < length; i++) {
      /* Step 2 */
      someHasNotHadIWS = someHasNotHadIWS || !p_iws;
      c = data[i]; // current char
      c_iws = hasAttribute(c, PU_IWS);
      c_wsf = hasAttribute(c, PU_WSF);
      c_ucf = hasAttribute(c, PU_UCF);
      c_ucw = hasAttribute(c, PU_UCW);
      c_lst = hasAttribute(c, PU_LST);
      c_nsi = hasAttribute(c, PU_NSI);
      int step;
      if (someHasNotHadIWS) {
        step = 3;
      } else if (c_iws){
        step = 8;
      } else {
        p = Character.toLowerCase(c);
        p_ucw = false;
        c_ucw = false;
        p_iws = c_iws;
        p_wsf = c_wsf;
        p_ucf = c_ucf;
        p_lst = c_lst;
        p_nsi = c_nsi;
        step = 8;
      }

      switch (step) {
      case 3:
        /* Step 3 */
        if (step == 3) {
          if (p_wsf && c_iws) {
            step = 8;
          } else { // clear WSF attribute for p
            p_wsf = false;
            step = 4;
          }
        }
        
        /* Step 4 */
        if (step == 4) {
          if (p_ucf) {
            c = Character.toLowerCase(c);
            p_ucf = false;
          }
        
          /* Step 5 */
          if (p_ucw && c_iws) {
            p = Character.toLowerCase(p);
          }
          
          /* Step 6 */
          if (!(p_iws && c_iws)) {
            if (p != -1) output.append(p);
            p = c;
            step = 7;
          
            /* Step 7 */
            if (c_ucw && !p_iws) c_ucw = false;
            p_iws = c_iws;
            p_wsf = c_wsf;
            p_ucf = c_ucf;
            p_ucw = c_ucw;
            p_lst = c_lst;
            p_nsi = c_nsi;
          }
        }
        /* Falls through */
      
      case 8:
        /* Step 8 */
        if (i + 1 >= length && someHasNotHadIWS && !p_iws && !p_lst) {
          if (p != -1) output.append(p);
        }
        
        /* Step 9 */
        break;
      }
    }
    return output.toArray();
  }
  
  /**
   * Method to check whether a value has a given attribute.
   * 
   * @param c A character
   * @param val See private field values
   * @return True if character has given attribute, false otherwise
   */
  private boolean hasAttribute(final int c, final int val) {
    return c == val;
  }

  /**
   * Method to check whether a value has a given attribute.
   * 
   * @param c A character
   * @param list See private field values
   * @return True if character has given attribute, false otherwise
   */
  private boolean hasAttribute(final int c, final int[] list) {
    int size = list.length;
    for (int i = 0; i < size; i++) {
      if (c == list[i]) return true;
    }
    return false;
  }
  
  /**
   * This will use the punctuation processor to decompress data.
   * 
   * @param data The compressed user data
   * @return The compressed data
   */
  public IntArrayBuilder decompress(final int[] data) {
    return decompress(new IntArrayBuilder(data));
  }
  
  /**
   * This will use the punctuation processor to decompress data.
   * 
   * @param data The compressed user data
   * @return The compressed data
   */
  public IntArrayBuilder decompress(final IntArrayBuilder data) {
    int length = data.length();
    final IntArrayBuilder output = new IntArrayBuilder(length);
    boolean someHasNotHadIWS = false;
    int p = -1; // previous character
    int c = -1; // current character
    boolean p_iws = false; boolean c_iws = false;
    boolean p_wsf = false; boolean c_wsf = false;
    boolean p_ucf = false; boolean c_ucf = false;
    boolean p_ucw = false; boolean c_ucw = false;
    boolean p_lst = false; boolean c_lst = false;
    boolean p_nsi = false; boolean c_nsi = false;
    boolean p_createdByStep10 = false;
    boolean c_createdByStep10 = false;
    boolean created = false;
    
    /* Step 1 */
    for (int i = 0; i < length; i++) {
      /* Step 2 */
      someHasNotHadIWS = someHasNotHadIWS || !p_iws;
      c = data.intAt(i); // current char
      c_iws = hasAttribute(c, PU_IWS);
      c_wsf = hasAttribute(c, PU_WSF);
      c_ucf = hasAttribute(c, PU_UCF);
      c_ucw = hasAttribute(c, PU_UCW);
      c_lst = hasAttribute(c, PU_LST);
      c_nsi = hasAttribute(c, PU_NSI);
      if (created) {
        created = false;
      } else {
        p_createdByStep10 = c_createdByStep10;
        c_createdByStep10 = false;
      }
      int step = 3;
      if (i == 0) {
        c = Character.toUpperCase(c);
        step = 8;
      }
      
      switch (step) {
      case 3:
        /* Step 3 */
        if (c_iws && p_ucw) {
          p = Character.toUpperCase(p);
        }
        
        /* Step 4 */
        if (p_ucf && !c_createdByStep10) {
          c = Character.toUpperCase(c);
          p_ucf = false;
        }
        
        /* Step 5 */
        step = (p_createdByStep10 && c_nsi ? 7 : 6);
        
        /* Step 6 */
        if (step == 6 && p != -1) output.append(p);
        
        /* Step 7 */
        p_ucw = (p_iws && c_ucw ? c_ucw : false);
        /* Falls through */
        
      case 8:
        /* Step 8 */
        p = c;
        
        /* Step 9 */
        if (c_ucf) p_ucf = c_ucf;
        
        /* Step 10 */
        if (c_wsf && i + 1 < length) {
          data.insert(i + 1, PU_IWS);
          length = data.length();
          p_createdByStep10 = c_createdByStep10;
          c_createdByStep10 = true;
          created = true;
        }
        
        /* Step 11 */
        if (i + 1 < length) continue;
        
        /* Step 12 */
        if (p != -1) output.append(p);
        if (!c_ucf || p == PU_LST) {
          output.append(PU_LST);
        }
        return output;
      }
    }
    return output;
  }
}